/**
 * This software was produced for the U. S. Government
 * under Contract No. FA8721-09-C-0002, and is
 * subject to the Rights in Noncommercial Computer Software
 * and Noncommercial Computer Software Documentation Clause
 * (DFARS) 252.227-7014 (JUN 1995)
 * 
 * (c) 2009 The MITRE Corporation. All Rights Reserved.
 *
 * SPMIF to OPNET Translator
 * Author: EWALTERS
 * Created: 10:12:32 AM, Aug 1, 2007
 * MITRE Corporation
 */
package com.mitre.spmif2opnet.analysis.ast;

import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;

import javax.xml.datatype.XMLGregorianCalendar;

import com.mitre.spmif2opnet.analysis.AnalysisException;
import com.mitre.spmif2opnet.analysis.Identifier;



/**
 * Each EGGraph from S-PMIF is mapped to a single task/graph
 * These tasks are then analyzed for dependencies, and mapped to OPNET-modelable tasks 
 */
public class Graph implements Copyable<Graph> {
	/**
	 * Graph name
	 */
	private String _name;
	/**
	 * Graph identifier
	 */
	private Identifier _id;
	/**
	 * Is this the main graph in its containing scenario?
	 */
	private boolean _is_main;
	/**
	 * What is the id of the start node here?
	 */
	private String _start_node_id;
	/**
	 * What is the mod date/time?
	 */
	private XMLGregorianCalendar _mod_date_time;
	/**
	 * What is the model name of the graph
	 */
	private String _model_name;
	/**
	 * Contains node information, indexed by node name
	 */
	Hashtable<String, Node> _nodes;
	/**
	 * Contains edge information, indexed by edge name
	 */
	Hashtable<String, List<String>> _edges;
	
	/**
	 * Default constructor
	 * @param name
	 * @param id
	 * @param is_main
	 * @param start_node_id
	 * @param mod_date_time
	 * @param model_name
	 */
	public Graph(String name, Identifier id, boolean is_main, String start_node_id, 
			XMLGregorianCalendar mod_date_time, String model_name) {
		_name = name;
		_id = new Identifier(id);
		_is_main = is_main;
		_start_node_id = start_node_id;
		_mod_date_time = mod_date_time;
		_model_name = model_name;
		_nodes = new Hashtable<String, Node>();
		_edges = new Hashtable<String, List<String>>();
	}
	
	/**
	 * Id accessor
	 * @return the id
	 */
	public String getId() {
		return _id.getGraphId();
	}
	
	/**
	 * Name accessor
	 * @return the name
	 */
	public String getName() {
		return _name;
	}
	
	/**
	 * Identifier accessor
	 * @return the id
	 */
	public Identifier getIdentifier() {
		return _id;
	}
	
	/**
	 * Get the start node
	 * @return the node
	 */
	public Node getStartNode() {
		return _nodes.get(_start_node_id);
	}
	
	/**
	 * Get a node from the string id
	 * @param id the id
	 * @return the node
	 * @throws AnalysisException
	 */
	public Node getNode(String id) throws AnalysisException {
		Node n = _nodes.get(id);
		if (n == null) {
			throw new AnalysisException("Attempt to access non-existent node " 
					+ id + " from task " + _name);
		}
		return _nodes.get(id);
	}
	
	/**
	 * NOTE: We have pushed the error checks for getting nodes and such down
	 * to the lowest appropriate level, e.g. nodes via tasks, tasks via scenarios, etc.
	 * We do this because >any< time we try to access a node that isn't there,
	 *  it is an error condition!
	 * Therefore, if we return, the node is valid!
	 * Get a node from the graph
	 * @param id the identifier
	 * @return the node
	 * @throws AnalysisException
	 */
	public Node getNode(Identifier id) throws AnalysisException {
		if (!id.getGraphId().equals(_id.getGraphId())) {
			throw new AnalysisException("Attempt to access graph " 
					+ _id.getGraphId() + " with incorrect identifier " 
					+ id.toString());
		}
		return this.getNode(id.getGraphId());
	}

	/**
	 * Add a node to the graph
	 * @param node the node
	 */
	public void addNode(Node node) {
		// Check and abort on dupes
		if (_nodes.containsKey(node.getId())) {
			System.out.println("[WARNING] Duplicate Node " + node.getName()
					+ " in Graph " + _name);
			return;
		}

		_nodes.put(node.getId(), node);
	}
	/**
	 * Add an edge to the graph
	 * @param from_node
	 * @param to_node
	 */
	public void addEdge(String from_node, String to_node) {
		// Check node info to see if both nodes exist
		if (!_nodes.containsKey(from_node) || !_nodes.containsKey(to_node)) {
			System.out.println("[ERROR] Invalid Arc in Graph " + _name +
					" from node " + from_node.toString() + " to node " + to_node.toString());
			return;
		}

		// Add the edge to the edge list
		// NOTE: Do we need to check for duplicate edges?
		List<String> edge_list = _edges.get(from_node);
		if (edge_list == null) {
			edge_list = new LinkedList<String>();
		}
		edge_list.add(to_node);
		_edges.put(from_node, edge_list);
	}
	
	/**
	 * Get the successor nodes to a given node
	 * @param n the node
	 * @return its successors
	 */
	public List<Node> getSuccessors(Node n) {
		List<Node> succ = new LinkedList<Node>();
		String id = n.getId();
		if (_edges.containsKey(id)) {
			for (String s : _edges.get(id)) {
				succ.add(_nodes.get(s));
			}
		}
		return succ;
	}

	/**
	 * Is this node the main one?
	 * @return is it?
	 */
	public boolean isMain() {
		return _is_main;
	}
	
	/**
	 * How many nodes are in this graph?
	 * @return the nuber
	 */
	public int numNodes() {
		return _nodes.size();
	}
	
	/**
	 * Copy the graph - useful for instantiating multiple expanded nodes
	 */
	public Graph copy() {
		Identifier id_new = new Identifier(_id);
		id_new.setGraphId(Identifier.newGraphId(_id.getGraphId()));
		Graph g_new = new Graph(_name, id_new, _is_main, _start_node_id, _mod_date_time, _model_name);
		// Loop through the nodes, caching the new names so we can change the edges, and fix
		//  the start_node_id
		Hashtable<String, String> node_map = new Hashtable<String, String>();
		for (Node n : _nodes.values()) {
			Node new_n = n.copy();
			g_new.addNode(new_n);
			node_map.put(n.getId(), new_n.getId());
		}
		for (String from_node : _edges.keySet()) {
			for (String to_node : _edges.get(from_node)) {
				g_new.addEdge(node_map.get(from_node), node_map.get(to_node));
			}
		}
		g_new._start_node_id = node_map.get(_start_node_id);
		return g_new;
	}
}
